<script setup>
import MessageComp from './components/MessageComp.vue'
import { Plus, Brush, EditPen, Delete, Promotion } from '@element-plus/icons-vue'
import { ref, onMounted, nextTick } from 'vue'
import OpenAI from 'openai'
import { ElMessage, ElMessageBox } from 'element-plus'
import { MODEL_CONFIG, API_CONFIG as DEEPSEEK_CONFIG, STORAGE_KEY } from '../../config/deepseek'

//响应数据
const sessionList = ref([])
const activeIndex = ref(-1) //激活索引
const editIndex = ref(-1) //编辑索引
const querykeys = ref('') //查询关键字
const openai = ref(null) //openai实例
const loading = ref(false) //加载状态
const messageRef = ref(null) //消息组件
const queryInfos = ref({
  messages: [],
  model: 'deepseek-r1',
  ...MODEL_CONFIG,
})

//初始化会话列表
const initSessionList = () => {
  sessionList.value = JSON.parse(localStorage.getItem(STORAGE_KEY.sessionList) || '[]')
}
//初始化索引
const initIndex = () => {
  //本地存储获取会话列表长度
  const listLen = JSON.parse(localStorage.getItem(STORAGE_KEY.sessionList) || '[]').length
  //本地存储获取激活索引
  const lastIndex = JSON.parse(localStorage.getItem(STORAGE_KEY.activeIndex) || '-1')
  //如果会话列表长度大于0，则更新为上一次的索引
  if (listLen > 0) activeIndex.value = lastIndex
  else activeIndex.value = -1
  //如果活动索引不为-1，则更新消息列表
  if (activeIndex.value != -1)
    queryInfos.value.messages = sessionList.value[activeIndex.value].messages
}

//新增会话
const handleAddSession = () => {
  //检查是否有查询正在进行
  if (loading.value) {
    ElMessage.warning('查询正在进行，请完成后再试')
    return
  }
  //创建一个新会话对象，并添加到会话列表中
  sessionList.value.push({
    title: `对话${sessionList.value.length + 1}`,
    crtTime: new Date(),
    messages: [],
  })
  //清空查询信息列表，准备新对话
  queryInfos.value.messages = []
  //设置活动对话索引为新添加的索引
  activeIndex.value = sessionList.value.length - 1
}

//编辑对话框
const handleFocusIndex = (index) => {
  //设置编辑索引
  editIndex.value = index
}

//切换对话框
const handleChangeSessionIndex = async (index) => {
  //检查是否有查询正在进行
  if (loading.value) {
    ElMessage.warning('查询正在进行，请完成后再试')
    return
  }
  //设置活动对话索引
  activeIndex.value = index
  //更新消息列表
  queryInfos.value.messages = sessionList.value[index].messages || []
  //等待vue完成对dom的渲染
  await nextTick()
  if (messageRef.value) {
    //滚动到底部
    messageRef.value.scrollToBottom()
  }
}

//删除会话
const handleDeleteSession = async (index) => {
  //检查是否有查询正在进行
  if (loading.value) {
    ElMessage.warning('查询正在进行，请完成后再试')
    return
  }
  //弹出确认框
  ElMessageBox.confirm('是否删除当前会话？', '温馨提示', {
    confirmButtonText: '确定',
    cancelButtonText: '取消',
    type: 'warning',
  })
    .then(() => {
      //删除会话
      sessionList.value.splice(index, 1)
      //如果删除的是活动对话，则将活动索引指向新的会话索引
      if (activeIndex.value == index) {
        activeIndex.value = sessionList.value[index] ? index : --index
      } else if (activeIndex.value > index) {
        activeIndex.value--
      }
      //更新当前活动会话列表
      queryInfos.value.messages =
        activeIndex.value > -1 ? sessionList.value[activeIndex.value].messages : []
      //切换索引
      handleChangeSessionIndex(activeIndex.value)
    })
    .catch((err) => {
      console.log(err)
    })
}

//当前配置
const currentConfig = ref(DEEPSEEK_CONFIG)

//初始化opanai实例
const initOpenAI = () => {
  openai.value = new OpenAI({
    ...currentConfig.value,
  })
}

//发送请求
const handleRequest = async () => {
  if (!querykeys.value) {
    ElMessage.error('请先输入查询内容')
    return
  }
  //确保openai实例存在
  if (!openai.value) {
    initOpenAI()
    if (!openai.value) {
      ElMessage.error('初始化openai失败，请稍后再试')
      return
    }
  }

  if (!sessionList.value.length) {
    await handleAddSession()
  }
  queryInfos.value.messages.push({
    role: 'user',
    content: querykeys.value,
    name: '问答助手',
  })
  querykeys.value = ''
  try {
    loading.value = true
    queryInfos.value.messages.push({
      role: 'assistant',
      content: '',
    })
    //封装请求参数
    const requestConfig = {
      ...queryInfos.value,
      stream: true,
    }
    const response = await openai.value.chat.completions.create(requestConfig)

    for await (const part of response) {
      if (part && part.choices && part.choices.length > 0) {
        const choice = part.choices[0]
        const latestMessage = queryInfos.value.messages[queryInfos.value.messages.length - 1]
        const reasonContent = choice.delta.reasoning_content || ''
        const content = choice.delta.content || ''

        latestMessage.content += reasonContent + content
      }
    }

    sessionList.value[activeIndex.value].messages = queryInfos.value.messages

    //启用提交按钮
    loading.value = false
  } catch {
    //重新打开按钮
    loading.value = false
  }
}

const handleClearStorage = () => {}

//清空会话
const handleClearSession = (index) => {
  console.log(index)
}

//组件挂载完毕
onMounted(() => {
  initSessionList()
  initIndex()
})
</script>

<template>
  <div class="inner-html-container">
    <div class="page">
      <!--提示内容 start-->
      <div class="tips">
        <div class="title">Vue3+deepseek从0到1Ai问答系统</div>
        <div class="desc">本网站采用本地缓存模式运行，不会侵犯个人隐私</div>
        <el-button type="primary" @click="handleClearStorage">清空</el-button>
      </div>
      <!--提示内容 end-->
      <!--详细内容 start-->
      <div class="grid-space-between grid-box">
        <!--左侧内容 start-->
        <div class="left-container">
          <el-button
            type="primary"
            class="add-btn"
            :icon="Plus"
            size="large"
            @click="handleAddSession"
            >新建对话</el-button
          >
          <div class="session-area">
            <div
              class="session-item"
              :class="activeIndex === index ? 'session-item-active' : ''"
              v-for="(item, index) in sessionList"
              :key="`session_${index}`"
              @click="handleChangeSessionIndex(index)"
            >
              <span
                :class="activeIndex === index ? 'active-node' : 'normal-node'"
                v-if="editIndex !== index"
                >{{ item.title }}</span
              >
              <el-input
                v-else
                v-model="item.title"
                size="small"
                autofocus
                style="width: 120px"
                @change="editIndex = -1"
                @blur="editIndex = -1"
              ></el-input>
              <div class="icon-box">
                <el-icon class="icon" color="#fff" @click.stop="handleClearSession(index)"
                  ><Brush
                /></el-icon>
                <el-icon class="icon" color="#fff" @click.stop="handleFocusIndex(index)"
                  ><EditPen
                /></el-icon>
                <el-icon class="icon" color="#fff" @click.stop="handleDeleteSession(index)"
                  ><Delete
                /></el-icon>
              </div>
            </div>
          </div>
        </div>
        <!--左侧内容 end-->
        <!--右侧内容 start-->
        <div class="right-container">
          <div class="message-area">
            <MessageComp
              ref="messageRef"
              :message="queryInfos.messages"
              :loading="loading"
            ></MessageComp>
          </div>
          <div class="input-area">
            <el-input
              v-model="querykeys"
              id="keyInput"
              placeholder="请输入内容"
              @keyup.enter="
                (e) => {
                  if (e.isComposing || loading) return
                  else handleRequest()
                }
              "
              show-word-limit
            />
            <el-button
              style="height: 40px"
              type="primary"
              :loading="loading"
              @click="handleRequest"
            >
              <el-icon><Promotion /></el-icon>
            </el-button>
          </div>
        </div>
        <!--右侧内容 end-->
      </div>
      <!--详细内容 end-->
    </div>
  </div>
</template>

<style scoped>
.inner-html-container {
  width: 100%;
  height: 100%;
  display: flex;
  /**子元素水平居中 */
  justify-content: center;
  /**垂直居中 */
  align-items: center;
}

.add-btn {
  width: 100%;
  font-size: 15px;
  font-weight: bold;
}
.page {
  width: 94vw;
  height: 94vh;

  /**设置盒子模型 */
  box-sizing: border-box;
  border-radius: 12px;
  overflow: hidden;
}

.tips {
  width: 100%;
  height: 40px;
  /**线性渐变 */
  background: linear-gradient(90deg, #e99d53, #a4bd27);
  color: #fff;
  display: flex;
  /**水平排列 */
  justify-content: space-between;
  align-items: center;
  /**设置内边距 上下距0 左右距16px */
  padding: 0 16px;
  box-sizing: border-box;
}
.tips .title {
  font-size: 18px;
  font-weight: bold;
}

.tips .desc {
  font-size: 14px;
  color: rgba(255, 255, 255, 0.9);
}

.grid-box {
  display: grid;
  grid-template-columns: 280px auto;
  gap: 16px;
  padding: 16px;
}

.grid-space-between {
  width: 100%;
  height: calc(100% - 40px);
}

/**左边列表样式 */
.left-container {
  background-color: #ddd;
  padding: 16px;
  border-radius: 8px;
  height: calc(94vh - 40px - 32px);
}
.left-container .session-area {
  margin-top: 16px;
  height: calc(100%-56px);
  overflow-y: auto;
}
.left-container .session-item {
  display: flex;
  padding: 8px 12px;
  margin-bottom: 8px;
  align-items: center;
  justify-content: space-between;
  border-radius: 6px;
  background: rgb(249, 251, 255);
  cursor: pointer;
  /**过渡效果 */
  transition: all 0.3s ease;
}

.left-container .session-item-active {
  background: rgb(222, 233, 251);
  border: 1px solid rgba(30, 147, 106, 0.3);
}
/**图标样式 start */
.session-item .icon-box {
  display: flex;
  gap: 8px;
  opacity: 0;
  transition: opacity 0.3s ease;
}
.session-item .icon-box:hover {
  opacity: 1;
}
.icon-box .icon {
  color: #a4bd27;
  font-size: 16px;
  cursor: pointer;
  transition: all 0.3s ease;
}

.icon:hover {
  /**设置缩放效果 */
  transform: scale(1.2);
  color: #e99d53 !important;
}
/**图标样式 end */

/**按钮样式 start*/
:deep(.el-button) {
  border-radius: 8px;
  transition: all 0.3s ease;
}

:deep(.el-button:hover) {
  /**向上平移 */
  transform: translateY(-1px);
  box-shadow: 0 0 12px rgba(208, 204, 8, 0.5);
}

:deep(.el-button:active) {
  transform: translateY(1px);
}
:deep(.el-button.is-disabled) {
  background-color: rgba(198, 204, 8, 0.5);
  border-color: rgba(198, 204, 8, 0.5);
}

/**右边样式 start */
.right-container {
  width: 100%;
  height: calc(94vh - 40px);
  background: #e6dfdf;
  border-radius: 8px;
  padding: 16px;
  box-sizing: border-box;
}
.right-container .message-area {
  height: calc(100% - 45px - 8px - 36px);
  width: calc(100% - 12px);
  padding: 8px 8px 0 8px;
}
.input-area {
  height: 36px;
  display: grid;
  grid-template-columns: auto 120px 80px;
  grid-gap: 10px;
  box-sizing: border-box;
  padding: 0 8px 8px 0;
}

:deep(.el-input__wrapper) {
  height: 40px;
  line-height: 40px;
  background-color: #cee0e6;
  box-shadow: 0 0 8px rgba(178, 177, 159, 0.5);
  padding: 0 12px;
  transition: all 0.3s ease;
}

:deep(.el-input__wrapper .el-input__inner) {
  height: 40px;
  line-height: 40px;
  color: #111210;
  font-size: 14px;
}
/**右边样式 end */
</style>
